package EP.Ontologia;

import SBD.CRUD.OntologiaBD;
import SO.Operaciones.OperacionesOntologia;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jdom.JDOMException;

/**
* @class Ontología
* @author Mauricio Alvarez.
* @brief Esta clase representa la clase Ontología, correspondiente a una entidad principal (EP).
*/
public class Ontologia
{
    /**
     *  @var private int id_termino
     *  @brief id de la tabla ontología (PK).
    */
    private int id_termino;
    /**
     *  @var private int id_padre
     *  @brief id del padre del término dentro de la ontología.
    */
    private int id_padre;
    /**
     *  @var private String termino
     *  @brief Concepto dentro del grafo de ontología.
    */
    private String termino;
    /**
     *  @var private String cantidad_hijos
     *  @brief Cantidad de descendientes que tiene un concepto dentro de la ontología.
    */
    private String cantidad_hijos;
    /**
     *  @var private int nivel
     *  @brief Nivel dentro del grafo ontologíco XML donde se ubica el concepto al ser parseado.
    */
    private int nivel;
    /**
     *  @var private Map<String,List> ontologia
     *  @brief Map que almacena la ontología.
    */
    private Map<String,List> ontologia = new HashMap();
    /**
     *  @var private Map<String,List> topico
     *  @brief Map que almacena un tópico de la ontología.
    */
    private Map<String,List> topico = new HashMap();
    /**
     *  @var private Map<Integer, List> SubtopicosRamas
     *  @brief Map que contiene las ramas.
    */
    private Map<Integer, List> SubtopicosRamas = new HashMap();
    /**
     *  @var private int contador_ramas
     *  @brief contador de ramas generadas.
    */
    private int contador_ramas;
    /**
     *  @var private OperacionesOntologia opjdom
     *  @brief Variable global de tipo clase OperacionesOntologia.
    */
    private OperacionesOntologia opjdom;
    /**
     *  @var private OntologiaBD ontoBD
     *  @brief Variable global de tipo clase OntologiaBD.
    */
    private OntologiaBD ontoBD;


    //////////////////////
    // CONSTRUCTOR
    //////////////////////
    public Ontologia() 
    {
        opjdom = new OperacionesOntologia();
        ontoBD = new OntologiaBD();
    }

    
    //////////////////////
    // GETTER Y SETTER
    //////////////////////
    public String getCantidad_hijos() {
        return cantidad_hijos;
    }

    public void setCantidad_hijos(String cantidad_hijos) {
        this.cantidad_hijos = cantidad_hijos;
    }

    public int getId_padre() {
        return id_padre;
    }

    public void setId_padre(int id_padre) {
        this.id_padre = id_padre;
    }

    public int getId_termino() {
        return id_termino;
    }

    public void setId_termino(int id_termino) {
        this.id_termino = id_termino;
    }

    public int getNivel() {
        return nivel;
    }

    public void setNivel(int nivel) {
        this.nivel = nivel;
    }

    public String getTermino() {
        return termino;
    }

    public void setTermino(String termino) {
        this.termino = termino;
    }


    //////////////////////
    // OPERACIONES
    //////////////////////

    /**
     * @brief Guarda la ontología en la BD y corrige algunos errores de parseo y estructura.
     * @param no tiene.
     * @return void
    */
    public void GuardarOntologia() throws JDOMException, IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException
    {
        
        ////////////////////////////////////////////////////////////////////////
        //PASO 1: Se carga la la raiz de la ontología del archivo XML
        //        correspondiente al PADRE de los Tópicos. (Ver estructura
        //        de la ontología):
        ////////////////////////////////////////////////////////////////////////
        List Topicos = opjdom.CargarRaizOntologia();
        //////////////////////////////////////////////////////////////////////
        //PASO 2: Se parsean los tópicos en busca de sus hijos.
        //////////////////////////////////////////////////////////////////////
        opjdom.parseoXML(Topicos, 1, 0);
        //////////////////////////////////////////////////////////////////////
        //PASO 3: Se guarda en la base de datos los demás terminos
        //////////////////////////////////////////////////////////////////////
        ontoBD.GuardarOntologia(opjdom.RetornarOntologia());

        ///////////////////////////////////////////////
        // PASO 4: CORRECCIÓN ERRORES ONTOLOGÍA
        ///////////////////////////////////////////////

        //////////////////////////////////////////////////////////////////////      
        //Error 1: Se conectan los terminos que estan aislados donde se da el
        //         caso de id_padre = id_termino:
        //////////////////////////////////////////////////////////////////////
        ontoBD.ConectarConPadre(ontoBD.ConectarTerminos());
        //////////////////////////////////////////////////////////////////////
        //Error 2: Se corrige el caso de los terminos repetidos linkiando los
        //         duplicados con el termino original a través del id_padre.
        //////////////////////////////////////////////////////////////////////
        ontoBD.CorregirTerminosRepetidos();
        //////////////////////////////////////////////////////////////////////
        //Error 3: Se conectan los terminos que tienen como padre un termino
        //         que está en un nivel inferior: id_padre > id_termino
        //////////////////////////////////////////////////////////////////////
        ontoBD.ConectarConPadre(ontoBD.CorregirTerminosNivelPadre());
        //////////////////////////////////////////////////////////////////////
        //Error 4: Los padres de algunos terminos están en niveles demasiado
        //         distantes de los niveles hijos, aun cumpliendose que
        //         id_padre < id_termino. Se conectan estos terminos a padres
        //         del nivel superior donde se mantega dicha relación.
        //////////////////////////////////////////////////////////////////////
        ontoBD.ConectarConPadre(ontoBD.CorregirDiferenciaNiveles());
        //////////////////////////////////////////////////////////////////////
        //Error 5: Terminos que no pertenecen a los tópicos principales y que
        //         se encuentran en el nivel 1 y topicos principales que se
        //         encuentran en un nivel distinto al 1.
        //////////////////////////////////////////////////////////////////////
        ontoBD.ActualizarNivelTerminosNoTopicos(ontoBD.TerminosNoTopicosNivel1());
        ontoBD.ActualizarNivelTerminosTopicos(ontoBD.AscenderTopicosPrincipales());

        //////////////////////////////////////////////////////////////////////
        // PASO 5: Determinar los terminos y la cantidad de descendientes
        //         que tienen.
        //////////////////////////////////////////////////////////////////////
        Map<String,List> copia1_ontologia = new HashMap<String,List>();
        Map<String,List> copia2_ontologia = new HashMap<String,List>();
        
        copia1_ontologia = CargarOntologia();
        copia2_ontologia = CargarOntologia();

        ContarDescendientesTopico(copia1_ontologia, copia2_ontologia);

    }//FIN GuardarOntologia


    /**
     * @brief Borra completamente los datos de la ontología en la BD.
     * @param no tiene.
     * @return void
    */
    public void BorrarOntologia() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, SQLException
    {
        ontoBD.BorrarOntologia();
    }//FIN BorrarOntologia (OK)


    /**
     * @brief Carga completamente la ontología desde la BD.
     * @param no tiene.
     * @return Map<String,List>
    */
    public Map<String,List> CargarOntologia() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, SQLException
    {
        return ontoBD.CargarOntologia();

    }//FIN CargarOntologia


    /**
     * @brief Guarda las ramas sub-tópicos en la BD.
     * @param no tiene.
     * @return void
    */
    public void GuardarSubtopicos(List<String> ConceptosSemilla) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, SQLException
    {


        ////////////////////////////////////
        //PASO 1: Se carga la ontologia 
        ////////////////////////////////////
        ontologia = ontoBD.CargarOntologia();

        ////////////////////////////////////////////////////////////////////
        //PASO 2: Se extrae el tópico o subtopicos del arbol ontologico
        ////////////////////////////////////////////////////////////////////
        //Listado de terminos del topico proveniente del MAP Ontologia<String,List>
        List<Ontologia> ListadoTerminos = new ArrayList();
        //Listado de ids de los conceptos de una rama:
        List<Integer> ListadoConceptosRama_id = new ArrayList();
        //Listado de los conceptos de una rama:
        List<String> ListadoConceptosRama_t = new ArrayList();
        //Concepto semilla:
        String concepto;
        
        //Para cada concepto semilla se buscan las ramas:
        for (int j=0; j < ConceptosSemilla.size(); j++)
        {
            //Se extrae del List el concepto semilla:
            concepto = ConceptosSemilla.get(j);
            
            //Si el concepto se encuentra dentro de la ontología:
            if (ontologia.containsKey(concepto) == true)
            {
               //Se recorre el ArrayList en busca de los hijos:
               ListadoTerminos = ontologia.get(concepto);

               //Para cada concepto de la ontología se buscan los hijos:
               for (int i=0; i<ListadoTerminos.size(); i++)
               {
                   //El primer termino del tópico (Padre):
                   String termino = ListadoTerminos.get(i).getTermino().toLowerCase().replaceAll("á", "a").replaceAll("é", "e").replaceAll("í", "i").replaceAll("ó", "o").replaceAll("ú", "u");
                   int id_termino = ListadoTerminos.get(i).getId_termino();

                   //Se agregan a las listas correspondientes:
                   ListadoConceptosRama_id.add(id_termino);
                   ListadoConceptosRama_t.add(termino);

                   //Se consulta si en el tópico se ingreso el termino:
                   BusquedaTerminosSubTopico(id_termino, concepto, ListadoConceptosRama_id, ListadoConceptosRama_t);

                   //Se elimina el ultimo concepto para que no se almacene mal en
                   //las listas:
                   ListadoConceptosRama_id.remove(ListadoConceptosRama_id.size()-1);
                   ListadoConceptosRama_t.remove(ListadoConceptosRama_t.size()-1);

               }//FIN for

            }//FIN if            
        }

    }//FIN GuardarSubtopicos


    /**
     * @brief Borra las ramas sub-tópicos en la BD.
     * @param no tiene.
     * @return void
    */
    public void BorrarSubtopicos() throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, SQLException
    {
        ontoBD.BorrarSubtopicos();
    }


    /**
     * @brief Permite recorrer la ontología en busqueda de los subtópicos de
     * un tópico.
     * @param id_termino y el ListadoConceptosRama
     * @return void
    */
    public void BusquedaTerminosSubTopico(int id_termino,
                                          String ConceptoSemilla,
                                          List<Integer> ListadoConceptosRama_id,
                                          List<String> ListadoConceptosRama_t)
    {

        //Se establece flag para las hojas:
        boolean es_hoja = true;

        //Variables de ontologia:
        int id_padre_O;
        int id_termino_O;
        int nivel_O = 0;

        //Se clonan los listados:
        List<Integer> ListadoConceptosRama_id_aux = new ArrayList();
        ListadoConceptosRama_id_aux = ((List<Integer>) ((ArrayList) ListadoConceptosRama_id).clone());

        List<String> ListadoConceptosRama_t_aux = new ArrayList();
        ListadoConceptosRama_t_aux = ((List<String>) ((ArrayList) ListadoConceptosRama_t).clone());

        //Se Obtiene el listado de conceptos de la ontología:
        List<Ontologia> Listado = new ArrayList();
        Set set = ontologia.entrySet();
        Iterator i = set.iterator();

        //Se recorren los conceptos de la ontología:
        while(i.hasNext())
        {
          //Se obtiene el concepto:
          Map.Entry me = (Map.Entry)i.next();
          Listado = (List<Ontologia>) me.getValue();


          //Para el listado de instancias del concepto se compara con el id_termino:
          for(int j=0; j< Listado.size(); j++)
          {
              id_padre_O = Listado.get(j).getId_padre();
              id_termino_O = Listado.get(j).getId_termino();
              nivel_O = Listado.get(j).getNivel();

              //Si el id_padre del concepto extraido de la ontología es igual
              //al id_termino del concepto semilla:
              if (id_padre_O == id_termino)
              {
                  //Este concepto de la ontología NO es hoja:
                  es_hoja = false;

                  String concepto_O  = Listado.get(j).getTermino().toLowerCase().replaceAll("á", "a").replaceAll("é", "e").replaceAll("í", "i").replaceAll("ó", "o").replaceAll("ú", "u");

                  //Se agrega a las listas correspondientes:
                  ListadoConceptosRama_id_aux.add(id_termino_O);
                  ListadoConceptosRama_t_aux.add(concepto_O);

                  //Se sigue buscando:
                  BusquedaTerminosSubTopico(id_termino_O, ConceptoSemilla, ListadoConceptosRama_id_aux, ListadoConceptosRama_t_aux);

                  //Se elimina el ultimo concepto para que no se almacene mal en
                  //las listas:
                  ListadoConceptosRama_id_aux.remove(ListadoConceptosRama_id_aux.size()-1);
                  ListadoConceptosRama_t_aux.remove(ListadoConceptosRama_t_aux.size()-1);
                  

              }//FIN if

          }//FIN for

        }//FIN while


        //Si se recorrieron todos los conceptos de la ontología y aun el flag es
        //TRUE, significa que se llego a una hoja, por lo que se guardan la rama:

        //VARIANTE 1: Sin HCH como tópico principal: if (es_hoja == true)
        if (es_hoja == true)
        {
            //Aumento el contador de ramas:
            contador_ramas++;
            //Se guarda en el Map Subtopicos la rama encontrada:
            ontoBD.GuardarSubTopico(ConceptoSemilla, contador_ramas, ListadoConceptosRama_id_aux, ListadoConceptosRama_t_aux);

        }

    }//FIN BusquedaTerminosSubTopico


    /**
     * @brief Genera las Ramas en base a la tasa de muestreo y el nivel, las que
     * se usaran como consultas para las pruebas.
     * @param TasaMuestreo y Nivel.
     * @return void
    */
    public void GenerarRamasConsultas(int TasaMuestreo, int Nivel) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, SQLException
    {
        ////////////////////////////////////////////////////////////////////////
        //PASO 1: Se contabilizan la cantidad de terminos dentro de un nivel
        //        dado.
        ////////////////////////////////////////////////////////////////////////
        int Cantidad_Conceptos = ontoBD.ContarConceptosNivel(Nivel);

        ////////////////////////////////////////////////////////////////////////
        //PASO 2: Se extraen, al azar, las ramas del nivel ingresado de
        //        acuerdo a la tasa de muestreo.
        ////////////////////////////////////////////////////////////////////////
        List<Integer> NumeroRama = new ArrayList();
        NumeroRama = ontoBD.ExtraerRamasMuestreo(Nivel, Cantidad_Conceptos, TasaMuestreo);

        ////////////////////////////////////////////////////////////////////////
        //PASO 3: Se generan los subtopicos como consultas para probarlas en el
        //        filtro.
        ////////////////////////////////////////////////////////////////////////
        int N_RamasTotales = NumeroRama.size();

        for (int i=0; i < NumeroRama.size(); i++)
        {
            int N_rama = NumeroRama.get(i);
            ontoBD.GenerarConsultaRama(N_rama, N_RamasTotales, i, Nivel, Cantidad_Conceptos);
        }


    }//FIN GenerarRamasConsultas



    /**
     * @brief Permite obtener la cantidad de descendientes de algun concepto de la ontología.
     * @param Dos copias de la ontología original.
     * @return void
    */
    public void ContarDescendientesTopico(Map<String,List> copia1_ontologia, Map<String,List> copia2_ontologia) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, SQLException
    {

        //PASO 1: Mientras se recorre uno completo, se busca en el otro:
        int N_descendencia = 0;

        Set set1 = copia1_ontologia.entrySet();
        Iterator it1 = set1.iterator();

        //PARA EVITAR LOS BUCLES:
        //Se crea un HashMap que contenga los recorridos!:
        Map<Integer,Integer> terminosRecorridos = new HashMap<Integer,Integer>();

        int c=0;

        while(it1.hasNext())
        {
            
            System.out.println("Estoi en el termino: "+c);
            c++;

          //Se define el contador de descendencia:
          N_descendencia = 0;

          Map.Entry par1 = (Map.Entry)it1.next();

          //Se guarda el valor del par <Termino, Clase_Ontología> :
          List<Ontologia> ListadoTerminos = new ArrayList();
          ListadoTerminos = (List<Ontologia>) par1.getValue();


          //Para cada objeto de la clase en listado se buscan los hijos en el
          //copia2 de la ontología:
          for (int i=0; i<ListadoTerminos.size(); i++)
          {

              //System.out.println("Me cai 1 xD");
              //Se rescatan los atributos de la clase:
              int id_termino1 = ListadoTerminos.get(i).getId_termino();
              int id_padre1 = ListadoTerminos.get(i).getId_padre();
              String termino1 = ListadoTerminos.get(i).getTermino();


              //Se ingresa el termino como recorrido:
              terminosRecorridos.put(id_termino1, id_padre1);
              /////////////////////////////////////////////////////////////////


              //Se recorre la copia2 de la ontología buscando hijos:
              Set set2 = copia2_ontologia.entrySet();
              Iterator it2 = set2.iterator();

              while(it2.hasNext())
              {
                  //System.out.println("Me cai 2 xD");
                  Map.Entry par2 = (Map.Entry)it2.next();
                  List<Ontologia> ListadoAuxiliar = new ArrayList<Ontologia>();
                  ListadoAuxiliar = (List<Ontologia>) par2.getValue();

                  for (int j=0; j<ListadoAuxiliar.size(); j++)
                  {
                      //System.out.println("Me cai 3 xD");
                      //Se rescatan los atributos en copia2:
                      int id_termino2 = ListadoAuxiliar.get(j).getId_termino();
                      int id_padre2 = ListadoAuxiliar.get(j).getId_padre();

                      //Se pregunta si id_termino1 = id_padre2 para determinar
                      //si es hijo del concepto que se busca:
                      if (id_termino1 == id_padre2 )
                      {

                              //Aumenta en +1 la descendencia:
                              N_descendencia++;

                          //Si el termino YA FUE RECORRIDO...NO LO HAGO PO WN!...OBVIO...xD
                          if (terminosRecorridos.containsKey(id_termino2)==false)
                          {
                              //Se ingresa el termino como recorrido:
                              terminosRecorridos.put(id_termino2, id_padre2);

                              //Agrego la clase de este descendiente al concepto
                              //principal:
                              ListadoTerminos.add(ListadoAuxiliar.get(j));
                          }

                      }

                  }

              }//FIN while2

              //Se almacena la descendencia de este concepto en la BD:
              OntologiaBD ontoBD = new OntologiaBD();
              ontoBD.ActualizarDescendientes(id_termino1, N_descendencia);

          }//FIN for1

          //Se borra todo el contenido de terminosRecorridos para los terminos
          // i+1 en adelante, luego de que se analizó el termino i de la
          //ontología:
          terminosRecorridos.clear();

        }//FIN while1

    }//FIN ContarDescendientesTopico
    
    
    /**
     * @brief Permite extraer los todos los terminos descendientes de algun concepto de la ontología.
     * @param El tópico principal.
     * @return Map<String,List>
    */
    public Map<String,List> ExtraerConceptosTopico(String Topico) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException, SQLException
    {

        ////////////////////////////////////
        //PASO 1: Se carga la ontologia 
        ////////////////////////////////////
        ontologia = ontoBD.CargarOntologia();     //OK//

        /*
        Set set = ontologia.entrySet();
        Iterator it = set.iterator();
        int count=1;

        while(it.hasNext()){
          Map.Entry me = (Map.Entry)it.next();
          System.out.println(count+". Palabras del topico: "+ me.getKey()+" - Tamaño del arreglo: "+ ((List) me.getValue()).size());
          count++;
        }
        */

        ////////////////////////////////////////////////////////////////////
        //PASO 2: Se extrae el tópico o subtopicos del arbol ontologico
        ////////////////////////////////////////////////////////////////////
        List<Ontologia> ListadoTerminos = new ArrayList<Ontologia>();

        if (ontologia.containsKey(Topico) == true)
        {
           System.out.println("Si contiene topicop1 "+ ontologia.size());
           
           //Se recorre el ArrayList en busca de los hijos:
           ListadoTerminos = ontologia.get(Topico);


           for (int i=0; i<ListadoTerminos.size(); i++)
           {

               System.out.println(i+"...............................................................................Si contiene topicop2 "+ ListadoTerminos.size());
      
               //El primer termino del tópico (Padre):
               String termino = ListadoTerminos.get(i).getTermino().toLowerCase().replaceAll("á", "a").replaceAll("é", "e").replaceAll("í", "i").replaceAll("ó", "o").replaceAll("ú", "u");
               int id_termino = ListadoTerminos.get(i).getId_termino();
               
               //Se consulta si en el tópico se ingreso el termino:
               if (topico.containsKey(termino) == false)
               {
                   //Se ingresa por 1era vez:
                   topico.put(termino, null);
                   //Busco los hijos:
                   BusquedaTerminosTopico(id_termino);
               }else{
                   //Ya fue ingresado:
                   //topico.get(termino).add(ListadoTerminos.get(i));
                   //Busco los hijos:
                   BusquedaTerminosTopico(id_termino);
               }

           }//FIN for

        }//FIN if

        return topico;

    }//FIN ExtraerConceptosTopico


    /**
     * @brief Permite recorrer la ontología en busqueda de los terminos que pertenecen
     * a un tópico.
     * @param El id termino.
     * @return void
    */
    public void BusquedaTerminosTopico(int id_termino)
    {
        List<Ontologia> Listado = new ArrayList<Ontologia>();
        Set set = ontologia.entrySet();
        Iterator i = set.iterator();

        while(i.hasNext())
        {

          Map.Entry me = (Map.Entry)i.next();
          Listado = (List<Ontologia>) me.getValue();

          for(int j=0; j< Listado.size(); j++)
          {
              int id_padre_ListadoTerminos = Listado.get(j).getId_padre();
              int id_termino_ListadoTerminos = Listado.get(j).getId_termino();

              if (id_padre_ListadoTerminos == id_termino)
              {
                  String termino = Listado.get(j).getTermino().toLowerCase().replaceAll("á", "a").replaceAll("é", "e").replaceAll("í", "i").replaceAll("ó", "o").replaceAll("ú", "u");

                  //Se consulta si en el tópico se ingreso el termino:
                  if (topico.containsKey(termino) == false)
                  {
                      topico.put(termino, null);
                      BusquedaTerminosTopico(id_termino_ListadoTerminos);
                  }else{
                      //Ya fue ingresado:
                      //topico.get(termino).add((List) me.getValue());
                      //Busco los hijos:
                      BusquedaTerminosTopico(id_termino_ListadoTerminos);
                  }

              }//FIN if

          }//FIN for

        }//FIN while
        
    }//FIN BusquedaTerminosTopico


}//FIN CLASE Ontologia